#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <memory>

// separator of http protocol
class Separator
{
public:
    static const std::string lineSep;
    static const std::string statusFieldSep;
};
const std::string Separator::lineSep = "\r\n";
const std::string Separator::statusFieldSep = " ";

static const std::string wwwroot = "./wwwroot";
static const std::string homepage = "home.html";

class HttpRequest
{
    class LineParseMessage;
public:
    HttpRequest(std::string& request)
    {
        _init(request);
        _line_parse_message = std::make_shared<LineParseMessage>(_line);
    }
private:
    void _init(std::string& request)
    {
        std::string line;
        bool ok = _getline(request, &line);
        if(ok) {_line = line;}

        while(true)
        {
            ok = _getline(request, &line);
            if(ok)
            {
                if(line.empty())
                {
                    // the residual part of request is the content
                    _content = request;
                    break;
                }
                else
                {
                    // get the header line which is not empty
                    _headers.push_back(line);
                }
            }
            else
            {
                // the end of request
                break;
            }
        }
    }
public:
    void print() const
    {
        std::cout << "-----## HttpRequest: ##-----" << std::endl;
        std::cout << "line: " << _line << std::endl;
        for(const auto& header : _headers)
        {
            std::cout << "header: " << header << std::endl;
        }
        std::cout << "content: " << _content << std::endl;

        std::cout << "-----## LineParseMessage: ##-----" << std::endl;
        _line_parse_message->print();
    }

    std::shared_ptr<LineParseMessage> getLinePraseMessage() const
    {
        return _line_parse_message;
    }

    bool getFileContent(std::string* content) const
    {
        return _getFileContentHelper(_line_parse_message->_path, content);
    }

    static bool _getFileContentHelper(const std::string& path, std::string* content)
    {
        std::ifstream ifs(path, std::ios::binary);
        if(!ifs.is_open())
        {
            return false;
        }

        ifs.seekg(0, ifs.end);
        auto size = ifs.tellg();
        ifs.seekg(0, ifs.beg);

        content->resize(size);
        ifs.read(&(*content)[0], size);

        ifs.close();
        return true;
    }
private:
    bool _getline(std::string& request, std::string* line)
    {
        if(request.empty())
        {
            return false;
        }
        auto pos = request.find(Separator::lineSep);
        if(pos == std::string::npos)
        {
            return false;
        }

        *line = request.substr(0, pos);
        request.erase(0, pos + Separator::lineSep.size());
        return true;
    }
private:
    class LineParseMessage
    {
    public:
        using ptr = std::shared_ptr<LineParseMessage>;
    public:
        LineParseMessage(const std::string& line): _path(wwwroot)
        {
            std::stringstream ss(line);
            ss >> _method >> _url >> _version;  // stream insertion will be splited by space
            
            // parse the uri to get the path and suffix
            if(_url == "/")
            {
                _path += "/";
                _path += homepage;
            }
            else
            {
                _path += _url;
            }
            auto pos = _path.rfind('.');
            if(pos != std::string::npos)
            {
                _suffix = _path.substr(pos);
            }
            else
            {
                // default suffix is .html
                _suffix = ".html";
            }
        }

        void print() const
        {
            std::cout << "method: " << _method << std::endl;
            std::cout << "url: " << _url << std::endl;
            std::cout << "version: " << _version << std::endl;
            std::cout << "path: " << _path << std::endl;
            std::cout << "suffix: " << _suffix << std::endl;
        }
    public:
        std::string _method;  // such as GET, POST, PUT, DELETE, etc.
        std::string _url;
        std::string _version;  // such as HTTP/1.1
        std::string _path;  // the path of request uri, such as ./wwwroot/index.html, etc.
        std::string _suffix;  // the suffix of request uri, such as .html, .jpg, etc.
    };
private:
    std::string _line;  // method uri version
    std::vector<std::string> _headers;
    std::string _content;

    /// @brief this is a shared_ptr to LineParseMessage, which is used to parse the request line
    LineParseMessage::ptr _line_parse_message;
};

class HttpResponse
{
public:
    HttpResponse(): _line("HTTP/1.1 200 OK")
    {
        _line_parse_message = std::make_shared<LineParseMessage>(_line);
    }

    void print() const
    {
        std::cout << "-----## HttpResponse: ##-----" << std::endl;
        std::cout << "line: " << _line << std::endl;
        for(const auto& header : _headers)
        {
            std::cout << "header: " << header << std::endl;
        }
        std::cout << "content: " << _content << std::endl;
        std::cout << "-----## LineParseMessage: ##-----" << std::endl;
        _line_parse_message->print();
    }

    // ### set status line ###
    // set status line by 3 fields
    void setStatuLine(const std::string& version, int status_code, const std::string& code_desc)
    {
        _line_parse_message->setHttpVersion(version);
        _line_parse_message->setStatusCode(status_code);
        _line_parse_message->setCodeDesc(code_desc);
        _line = version + Separator::statusFieldSep + std::to_string(status_code) + Separator::statusFieldSep + code_desc;
    }
    // set status line by single line
    void setStatuLineSingleField(const std::string& line)
    {
        _line = line;
        // avoid the case that the line is not ended with \r\n, it will cause the edge analyze error
        if(line.find(Separator::lineSep) == std::string::npos)
        {
            _line += Separator::lineSep;
        }

        auto pos1 = line.find(Separator::statusFieldSep);
        _line_parse_message->setHttpVersion(line.substr(0, pos1));
        auto pos2 = line.find(Separator::statusFieldSep, pos1 + 1);
        _line_parse_message->setStatusCode(std::stoi(line.substr(pos1+1, pos2-pos1-1)));
        _line_parse_message->setCodeDesc(line.substr(pos2 + 1));
    }

    // ### set header ###
    void addHeader(const std::string& header)
    {
        _headers.push_back(header);
    }

    // ### set content ###
    void setContent(const std::string& content)
    {
        _content = content;
    }

    std::string serialize() const
    {
        /*
        *   http_status_line\r\n
        *   \r\n
        *   header_1\r\n
        *   header_2\r\n
        *   \r\n
        *   content\r\n
        */

        std::string response;

        response += _line;

        for(const auto& header : _headers)
        {
            response += header;
            if(header.find(Separator::lineSep) == std::string::npos)
            {
                response += Separator::lineSep;
            }
        }
        response += Separator::lineSep;

        response += _content;
        return response;
    }

    static std::string codeDesc(int status_code)
        {
            switch(status_code)
            {
                case 200:
                    return "OK";
                case 404:
                    return "Not Found";
                case 500:
                    return "Internal Server Error";
                default:
                    return "Unknown";
            }
        }

private:
    class LineParseMessage
    {
    public:
        using ptr = std::shared_ptr<LineParseMessage>;
    public:
        LineParseMessage(const std::string& line)
        {
            std::stringstream ss(line);
            ss >> _version >> _status_code >> _code_desc;  // stream insertion will be splited by space
        }

        void print() const
        {
            std::cout << "version: " << _version << std::endl;
            std::cout << "status_code: " << _status_code << std::endl;
            std::cout << "code_desc: " << _code_desc << std::endl;
        }

        void setHttpVersion(const std::string& version)
        {
            _version = version;
        }

        void setStatusCode(int status_code)
        {
            _status_code = status_code;
        }

        void setCodeDesc(const std::string& code_desc)
        {
            _code_desc = code_desc;
        }

    private:
        std::string _version;  // such as HTTP/1.1
        int _status_code;  // such as 200, 404, 500, etc.
        std::string _code_desc;  // such as OK, Not Found, Internal Server Error, etc.
    };

private:
    std::string _line;  // version status_code code_desc
    std::vector<std::string> _headers;
    std::string _content;
    LineParseMessage::ptr _line_parse_message;
};